/*
* Copyright (c) 2009, Jerry Hoff
* 
* All rights reserved.
* 
* Redistribution and use in source and binary forms, with or without 
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice, 
* 	 this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
*   this list of conditions and the following disclaimer in the documentation
*   and/or other materials provided with the distribution.
* - Neither the name of OWASP nor the names of its contributors may be used to
*   endorse or promote products derived from this software without specific
*   prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net;
using System.Text;
using System.Collections;

using org.owasp.validator.html.util;
using org.w3c.css.sac;
using org.owasp.validator.css;

using Parser = org.w3c.flute.parser.Parser;
using Selector = org.w3c.css.sac.Selector;
using CleanResults = org.owasp.validator.html.CleanResults;
using Policy = org.owasp.validator.html.Policy;
using ScanException = org.owasp.validator.html.ScanException;
using InputSource = org.w3c.css.sac.InputSource;

namespace org.owasp.validator.css
{

    /// <summary> A implementation of a SAC DocumentHandler for CSS validation. The appropriate
    /// validation method is called whenever the handler is invoked by the parser.
    /// The handler also builds a clean CSS document as the original CSS is scanned.
    /// 
    /// NOTE: keeping state in this class is not ideal as handler style parsing a la
    /// SAX should generally be event driven. However, there is not a fully
    /// implemented "DOM" equivalent to CSS at this time. Java has a StyleSheet class
    /// that could accomplish this "DOM" like behavior but it has yet to be fully
    /// implemented.
    /// 
    /// </summary>
    /// <seealso cref="javax.swing.text.html.StyleSheet">
    /// </seealso>
    /// <author>  Jason Li
    /// 
    /// </author>
    public class CssHandler : DocumentHandler
    {
        /// <summary> Returns the encapsulated results generated by the handler during parsing.
        /// 
        /// </summary>
        /// <returns> the <code>CleanResults</code> object containing the results
        /// generated by the handler
        /// </returns>
        virtual public CleanResults Results
        {
            get
            {
                // Always ensure results contain most recent generation of stylesheet                
                results.setCleanHTML(styleSheet.ToString());
                return results;
            }
			
        }
		
     /**
	 * The encaspulated results including the error messages
	 */
	
	private ArrayList errorMessages;

        /// <summary> The style sheet as it is being built by the handler</summary>

        private StringBuilder styleSheet = new StringBuilder();
		
        /// <summary> The validator to use when CSS constituents are encountered</summary>

        private CssValidator validator;
		
        /// <summary> The policy file to use in validation</summary>

        private Policy policy;
		
        /// <summary> The encaspulated results including the error messages</summary>

        private CleanResults results;
		
        /// <summary> A queue of imported stylesheets; used to track imported stylesheets</summary>

        private ArrayList importedStyleSheets;
		
        /// <summary> The tag currently being examined (if any); used for inline stylesheet
        /// error messages
        /// </summary>

        private string tagName;
		
        /// <summary> Indicates whether we are scanning a stylesheet or an inline declaration.
        /// true if this is an inline declaration; false otherwise
        /// </summary>
        
        private bool isInline;
		
        /// <summary> Indicates whether the handler is currently parsing the contents between
        /// an open selector tag and an close selector tag
        /// </summary>
        private bool selectorOpen = false;


        /**
         * Constructs a handler for stylesheets using the given policy and queue for
         * imported stylesheets.
         * 
         * @param policy
         *            the policy to use
         * @param embeddedStyleSheets
         *            the queue of stylesheets imported
         */
        public CssHandler(Policy policy, ArrayList embeddedStyleSheets,
            ArrayList errorMessages) :  this(policy, embeddedStyleSheets, errorMessages, null)
        {
        }

        /// <summary> Constructs a handler for inline style declarations using the given policy
        /// * and queue for imported stylesheets. 
        /// </summary>
        /// <param name="policy">policy <code>Policy</code> The policy to use
        /// </param>
        /// <param name="embeddedStyleSheets">embeddedStyleSheets <code>ArrayList</code> the queue of stylesheets imported
        /// </param>
        /// <param name="errorMessages">errorMessages <code>ArrayList</code> the queue of stylesheets imported
        /// </param>
        /// <param name="tagName">tagName <code>ArrayList</code> the associated tag name with this inline style
        /// </param>
        public CssHandler(Policy policy, ArrayList embeddedStyleSheets,
                ArrayList errorMessages, String tagName)
        {
            this.policy = policy;
            this.errorMessages = errorMessages;
            this.validator = new CssValidator(policy);
            this.importedStyleSheets = embeddedStyleSheets;
            this.tagName = tagName;
            this.isInline = (tagName != null);
        }

        /// <summary> Returns the cleaned stylesheet. 
        /// </summary>
        /// <returns> the cleaned stylesheet
        /// </returns>

        public String getCleanStylesheet()
        {
            // Always ensure results contain most recent generation of stylesheet
            return styleSheet.ToString();
        }




        /// <summary> Constructs a handler for stylesheets using the given policy and queue for
        /// imported stylesheets.
        /// 
        /// </summary>
        /// <param name="policy">the policy to use
        /// </param>
        /// <param name="embeddedStyleSheets">the queue of stylesheets imported
        /// </param>

        public CssHandler(Policy policy, ArrayList embeddedStyleSheets) : this(policy, embeddedStyleSheets, null)
        {
        }

        /// <summary> Constructs a handler for inline style declarations using the given policy
        /// and queue for imported stylesheets.
        /// 
        /// </summary>
        /// <param name="policy">the policy to use
        /// </param>
        /// <param name="embeddedStyleSheets">the queue of stylesheets imported
        /// </param>
        /// <param name="tagName">the associated tag name with this inline style
        /// </param>

        //public CssHandler(Policy policy, ArrayList embeddedStyleSheets, string tagName)
        //{
        //    this.policy = policy;
        //    //UPGRADE_TODO: The 'System.DateTime' structure does not have an equivalent to NULL. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1291'"
        //    //UPGRADE_NOTE: ref keyword was added to struct-type parameters. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1303'"
            
        //    //this.results = new CleanResults(ref null);
        //    //this.validator = new CssValidator(policy);
        //    this.importedStyleSheets = embeddedStyleSheets;
        //    this.tagName = tagName;
        //    this.isInline = (tagName != null);
        //}

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#comment(java.lang.String)
        */
        public virtual void comment(string text)
        {
            // validate comment
           
            //if (policy.getRegularExpression("cssCommentText").Pattern.matcher(text).matches())
            //{
            //    styleSheet.Append("/* " + text + " */\n");
            //}
            //else
            //{
            //    results.addErrorMessage("The comment field was filtered out for security reasons. The value of the comment field was <u>" + HTMLEntityEncoder.htmlEntityEncode(text) + "</u>");
            //}
            
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#ignorableAtRule(java.lang.String)
        */
        public virtual void ignorableAtRule(string atRule)
        {
            // this method is called when the parser hits an unrecognized
            // @-rule. Like the page/media/font declarations, this is
            // CSS2+ stuff

            //string msg = "The @-rule <u>" + HTMLEntityEncoder.htmlEntityEncode(atRule) + "</u> could not be processed for security reasons.";

            if (tagName != null)
            {
                /*
                errorMessages.Add(
                    ErrorMessageUtil.getMessage(ErrorMessageUtil.ERROR_CSS_TAG_RULE_NOTFOUND,
                    new Object[] { 
					HTMLEntityEncoder.htmlEntityEncode(tagName), 
					HTMLEntityEncoder.htmlEntityEncode(atRule)
				}));
                 */
                errorMessages.Add(HTMLEntityEncoder.htmlEntityEncode(tagName));
            }
            else
            {
                /*
                errorMessages.Add(ErrorMessageUtil.getMessage(
                    ErrorMessageUtil.ERROR_STYLESHEET_RULE_NOTFOUND,
                    new Object[] {  
					HTMLEntityEncoder.htmlEntityEncode(atRule)
				}));
                */
            }
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#importStyle(java.lang.String,
        *      org.w3c.css.sac.SACMediaList, java.lang.String)
        */

        public virtual void importStyle(string uri, SACMediaList media, string defaultNamespaceURI)
        {
            if (!Boolean.Parse(policy.getDirective("embedStyleSheets")))
            {
                //results.addErrorMessage("Importing of stylesheets has not been enabled");
            }
            //else if (!policy.getRegularExpression("offsiteURL").Pattern.matcher(uri).matches() && !policy.getRegularExpression("onsiteURL").Pattern.matcher(uri).matches())
            //{
            //    results.addErrorMessage("The url for stylesheet import could not be accepted for security reasons. The url was " + HTMLEntityEncoder.htmlEntityEncode(uri));
            //}
            //else
            //{

            //    try
            //    {
            //        URI importedStyleSheet = new URI(uri);

            //        // canonicalize the URI
            //        importedStyleSheet.normalize();

            //        if (!importedStyleSheet.isAbsolute())
            //        {
            //            // we have no concept of relative reference for free form
            //            // text as an end user can't know where the corresponding
            //            // free form will end up
            //            results.addErrorMessage("Cascading stylesheet located at <b>" + HTMLEntityEncoder.htmlEntityEncode(uri) + "</b> could not be embedded as relative URIs are not supported");
            //            return;
            //        }

            //        importedStyleSheets.Add(new InputSource(new InputStreamReader(importedStyleSheet.toURL().openStream())));
            //    }
            //    catch (URISyntaxException use)
            //    {
            //        results.addErrorMessage("Encountered invalid @import uri");
            //    }
            //    catch (System.IO.IOException ioe)
            //    {
            //        results.addErrorMessage("IO error importing stylesheet: " + uri);
            //    }
            //}
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#namespaceDeclaration(java.lang.String,
        *      java.lang.String)
        */
        public virtual void namespaceDeclaration(string prefix, string uri)
        {
            // CSS3 - Namespace declaration - ignore for now
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#startDocument(org.w3c.css.sac.InputSource)
        */
        public virtual void startDocument(InputSource arg0)
        {
            // no-op
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#endDocument(org.w3c.css.sac.InputSource)
        */
        public virtual void endDocument(InputSource source)
        {
            // no-op
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#startFontFace()
        */
        public virtual void startFontFace()
        {
            // CSS2 Font Face declaration - ignore this for now
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#endFontFace()
        */
        public virtual void endFontFace()
        {
            // CSS2 Font Face declaration - ignore this for now
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#startMedia(org.w3c.css.sac.SACMediaList)
        */
        public virtual void startMedia(SACMediaList media)
        {
            // CSS2 Media declaration - ignore this for now
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#endMedia(org.w3c.css.sac.SACMediaList)
        */
        public virtual void endMedia(SACMediaList media)
        {
            // CSS2 Media declaration - ignore this for now
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#startPage(java.lang.String,
        *      java.lang.String)
        */
        public virtual void startPage(System.String name, System.String pseudoPage)
        {
            // CSS2 Page declaration - ignore this for now
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#endPage(java.lang.String,
        *      java.lang.String)
        */
        public virtual void endPage(System.String name, System.String pseudoPage)
        {
            // CSS2 Page declaration - ignore this for now
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#startSelector(org.w3c.css.sac.SelectorList)
        */
        public virtual void  startSelector(SelectorList selectors)
        {
			
            // keep track of number of valid selectors from this rule
            int selectorCount = 0;
			
            // check each selector from this rule
            for (int i = 0; i < selectors.getLength(); i++)
            {
                ElementSelector selector = (ElementSelector) selectors.item(i);

                if (selector != null)
                {
                    string selectorName = selector.getLocalName();
					
                    // if the selector is valid, add to list
                    if (validator.isValidSelector(selectorName, selector, results))
                    {
                        if (selectorCount > 0)
                        {
                            styleSheet.Append(',');
                            styleSheet.Append(' ');
                        }
                        styleSheet.Append(selectorName);
						
                        selectorCount++;
                    }
                    else
                    {
                        if (tagName != null)
                        {
                            //ErrorMessage("The <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName) + "</b> tag had a <b>style</b> selector that was invalid. The value of <u>" + HTMLEntityEncoder.htmlEntityEncode(selector.ToString()) + "</u> could not be processed for security reasons.");
                        }
                        else
                        {
                            //results.addErrorMessage("The <b>style</b> tag had a selector that was invalid. The value <u>" + HTMLEntityEncoder.htmlEntityEncode(selector.ToString()) + "</u> could not be processed for security reasons.");
                        }
                    }
                }
            }

            // if and only if there were selectors that were valid, append
            // appropriate open brace and set state to within selector
            if (selectorCount > 0)
            {
                styleSheet.Append(' ');
                styleSheet.Append('{');
                styleSheet.Append('\n');
                selectorOpen = true;
            }
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#endSelector(org.w3c.css.sac.SelectorList)
        */
        public virtual void endSelector(SelectorList selectors)
        {
            // if we are in a state within a selector, close brace
            if (selectorOpen)
            {
                styleSheet.Append('}');
                styleSheet.Append('\n');
            }

            // reset state
            selectorOpen = false;
        }

        /*
        * (non-Javadoc)
        * 
        * @see org.w3c.css.sac.DocumentHandler#property(java.lang.String,
        *      org.w3c.css.sac.LexicalUnit, boolean)
        */
        public virtual void property(string name, LexicalUnit value_Renamed, bool important)
        {
            // only bother validating and building if we are either inline or within
            // a selector tag


            if (!selectorOpen && !isInline)
            {
                return;
            }

            // validate the property
            if (validator.isValidProperty(name, value_Renamed))
            {

                styleSheet.Append('\t');
                styleSheet.Append(name);
                styleSheet.Append(':');

                // append all values
                while (value_Renamed != null)
                {
                    styleSheet.Append(' ');
                    styleSheet.Append(validator.lexicalValueToString(value_Renamed));
                    value_Renamed = value_Renamed.getNextLexicalUnit();
                }
                styleSheet.Append(';');
                styleSheet.Append('\n');
            }
            else
            {

                if (tagName != null)
                {
                    //results.addErrorMessage("The <b>" + HTMLEntityEncoder.htmlEntityEncode(tagName) + "</b> tag had a style property that could not be accepted for security reasons. The <b>" + HTMLEntityEncoder.htmlEntityEncode(name) + "</b> property had a value of <u>" + HTMLEntityEncoder.htmlEntityEncode(validator.lexicalValueToString(value_Renamed)) + "</u>");
                }
                else
                {
                    //results.addErrorMessage("The <b>style</b> tag had a property <b>" + HTMLEntityEncoder.htmlEntityEncode(name) + "</b> that could not be accepted for security reasons. The property had a value of <u>" + HTMLEntityEncoder.htmlEntityEncode(validator.lexicalValueToString(value_Renamed)) + "</u>");
                }
            }
        }
    }
}
 